
import React, { ReactChild, ReactElement } from 'react';

import { IControledDOMProps } from '../../utils/props';

import { Icon } from '../icon';
import { CarouselItem, ICarouselItemProps } from './CarouselItem';
import { Dots } from './Dots';

const PLAY_INTERVAL = 3000;
const PLAY_DURATION = 300;

export interface ICarouselProps extends IControledDOMProps {
  className?: string;
  style?: React.CSSProperties;
  children: Array<React.ReactElement<CarouselItem>> | React.ReactNodeArray;
  autoPlay?: boolean;
  swipeable?: boolean;
  showDots?: boolean;
  showArrows?: boolean;
  onChange?: (index: number) => void;
  activeIndex?: number;
}

export interface ICarouselState {
  slideList: React.ReactNodeArray | ConcatArray<never> | never[];
  current?: number;
  carouselItemStyle: React.CSSProperties;
  canSlider: boolean;
  slideIndex: number;
  moveXDistance: number;
}

export class Carousel extends React.PureComponent<ICarouselProps, ICarouselState> {
  public static defaultProps = {
    className: '',
    style: {},
    autoPlay: true,
    swipeable: true,
    showDots: true,
    showArrows: true,
  };

  public static Item: typeof CarouselItem;
  public static Dots = Dots;

  // 如果传入了外部props，更新到state
  public static getDerivedStateFromProps(nextProps: ICarouselProps, prevState: ICarouselState) {
    const { activeIndex } = nextProps;
    if ((activeIndex !== undefined) && activeIndex !== prevState.slideIndex) {
      return {
        slideIndex: activeIndex,
        canSlider: true,
      };
    }
    return null;
  }

  public carouselNode: Element | null = null;
  public containerWidth: number;
  public timer: any;
  public startXPos: number;

  constructor(props: ICarouselProps) {
    super(props);
    this.state = {
      slideList: [],
      slideIndex: 0,
      canSlider: false,
      carouselItemStyle: {},
      moveXDistance: 0,
    };
    this.containerWidth = 0;
    this.timer = null;
    this.autoPlay = this.autoPlay.bind(this);
    this.mouseDown = this.mouseDown.bind(this);
    this.mouseUp = this.mouseUp.bind(this);
    this.mouseMove = this.mouseMove.bind(this);
    this.updateCurrentIndex = this.updateCurrentIndex.bind(this);
    this.onChange = this.onChange.bind(this);
    this.startXPos = 0;
  }

  /***
   * 轮播
   * @param direction: 轮播方向， left:向左，right:向右
   */
  public play(direction: string): void {
    const { slideList, slideIndex } = this.state;
    const len = slideList.length;
    if (direction === 'left') {
      this.updateCurrentIndex(slideIndex + 1);
      this.setState({
        canSlider: true,
      }, () => {
        if (len <= slideIndex + 2) {
          this.setState({
            canSlider: true,
          }, () => {
            window.setTimeout(() => {
              this.updateCurrentIndex(1);
              this.setState({
                canSlider: false,
              });
            }, PLAY_DURATION);
          });
        }
      });
    }
    if (direction === 'right') {
      this.updateCurrentIndex(slideIndex - 1);
      this.setState({
        canSlider: true,
      }, () => {
        if (slideIndex === 1) {
          this.setState({
            canSlider: true,
          }, () => {
            window.setTimeout(() => {
              this.updateCurrentIndex(len - 2);
              this.setState({
                canSlider: false,
              });
            }, PLAY_DURATION);
          });
        }
      });
    }
  }

  /***
   * 自动播放
   */
  public autoPlay(): void {
    const { autoPlay } = this.props;
    const { slideList } = this.state;
    if (autoPlay && slideList.length > 1) {
      window.clearInterval(this.timer);
      this.timer = window.setInterval(() => this.play('left'), PLAY_INTERVAL);
    }
  }

  /***
   * 鼠标按下
   */
  public mouseDown(e: MouseEvent): void {
    const { slideList } = this.state;
    if (slideList.length > 1) {
      window.clearInterval(this.timer);
      this.startXPos = e.pageX;
    }
  }

  /***
   * 鼠标移动
   */
  public mouseMove(e: MouseEvent): void {
    if (this.startXPos) {
      e.preventDefault();
      this.setState({
        moveXDistance: -(e.pageX - this.startXPos),
        canSlider: true,
      });
    }
  }

  /***
   * 鼠标释放
   */
  public mouseUp = (e: MouseEvent) => {
    const { moveXDistance } = this.state;
    e.preventDefault();
    if (Math.abs(moveXDistance) >= (this.containerWidth / 5)) {
      if (moveXDistance > 0) {
        this.play('left');
      } else {
        this.play('right');
      }
    }
    this.startXPos = 0;
    this.setState({
      moveXDistance: 0,
      canSlider: true,
    });
    this.autoPlay();
  }

  /***
   * 获取当前滚动到的轮播图索引
   */
  public onChange(index: number) {
    const { onChange } = this.props;
    if (onChange) {
      onChange(index);
    }
  }

  /***
   * 修改当前的轮播图索引
   */
  public updateCurrentIndex(index: number): void {
    const { activeIndex } = this.props;
    if (activeIndex === undefined) {
      this.setState({
        slideIndex: index,
      });
    }
    this.onChange(index);
  }

  /***
   * 获取滚动的轮播图列表
   */
  public getCarouselList(callback: () => void): void {
    const { children } = this.props;
    const length = React.Children.count(this.props.children);
    let arr: React.ReactNodeArray | ConcatArray<never> | never[] = [];
    if (!!length) {
      if (length > 1) {
        arr = [...children.slice(length - 1)].concat(children).concat(children[0]);
        this.setState({
          slideIndex: 1,
        });
      } else {
        arr = children;
      }
    }
    this.setState({
      slideList: arr,
    }, () => {
      callback();
    });
  }

  public componentDidMount() {
    const { swipeable } = this.props;
    this.containerWidth = this.carouselNode && this.carouselNode.clientWidth || 0;
    this.getCarouselList(() => {
      this.autoPlay();
      if (swipeable && this.carouselNode) {
        (this.carouselNode as HTMLElement).addEventListener('mousedown', this.mouseDown);
        (this.carouselNode as HTMLElement).addEventListener('mousemove', this.mouseMove);
        (this.carouselNode as HTMLElement).addEventListener('mouseup', this.mouseUp);
      }
      window.addEventListener('resize', () => {
        this.containerWidth = this.carouselNode && this.carouselNode.clientWidth || 0;
      });
    });
  }

  public componentDidUpdate(prevProps: ICarouselProps, prevState: ICarouselState) {
    if (this.props.activeIndex !== prevState.slideIndex) {
      window.clearInterval(this.timer);
      this.autoPlay();
    }
  }

  public componentWillUnmount() {
    if (this.timer) {
      window.clearInterval(this.timer);
      this.timer = null;
    }
  }

  public render() {
    const { className, children, style, showDots, showArrows, ...domProps } = this.props;
    const { slideList, moveXDistance, slideIndex, canSlider } = this.state;
    let listStyle = Object.assign({}, {
      width: slideList.length * this.containerWidth,
      transform: `translateX(-${slideIndex * this.containerWidth + moveXDistance}px)`,
    });
    return (
      <section
        {...domProps as IControledDOMProps}
        className={`br-carousel ${className}`}
        ref={(el: HTMLElement) => this.carouselNode = el} style={style}
        >
        <ul className={`br-carousel__list ${canSlider ? 'slider' : ''}`} style={listStyle}>
          {
            React.Children.map<ReactElement<ICarouselItemProps>>(
              slideList, (child: ReactChild) => {
                const ele = child as ReactElement<ICarouselItemProps>;
                return React.cloneElement(ele, {
                  style: { width: this.containerWidth },
                });
              })
          }
        </ul>
        {showArrows && this.renderArrows()}
        {showDots && children && <Dots length={children.length}
          activeIndex={slideIndex - 1} updateCurrentIndex={this.updateCurrentIndex} />}
      </section>
    );
  }

  public renderArrows() {
    return (
      <div className="br-carousel__arrow">
        <Icon
          onClick={() => this.play('right')}
          className="br-carousel__arrow-left"
          type="left"
        />
        <Icon
          onClick={() => this.play('left')}
          className="br-carousel__arrow-right"
          type="right"
        />
      </div>
    );
  }
}

Carousel.Item = CarouselItem;
